package cn.skyquant.quant4j.sdk.util.time;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 日期工具包
 *
 * @author harley-dog on 2015/6/4.
 */
public final class CalendarUtils {

    public static DateFormat dateFormat_US = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.US);

    private static DateFormat dateFormat_standard = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static Lock lock_standard = new ReentrantLock();
    public static String formatStandard(Date date) {
        String s = null;
        lock_standard.lock();
        s = dateFormat_standard.format(date);
        lock_standard.unlock();
        return s;
    }

    public static String formatStandard(long time) {
        String s = null;
        lock_standard.lock();
        s = dateFormat_standard.format(time);
        lock_standard.unlock();
        return s;
    }

    public static Date parseStandard(String str) throws ParseException {
        Date date = null;
        lock_standard.lock();
        date = dateFormat_standard.parse(str);
        lock_standard.unlock();
        return date;
    }


    private static DateFormat dateFormat_short_standard = new SimpleDateFormat("M-d H:m:s");
    private static Lock lock_short_standard = new ReentrantLock();
    public static String formatShortStandard(Date date) {
        String s = null;
        lock_short_standard.lock();
        s = dateFormat_short_standard.format(date);
        lock_short_standard.unlock();
        return s;
    }

    private static DateFormat dateFormat_full = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS E");
    private static Lock lock_full = new ReentrantLock();
    public static String formatFull(Date date) {
        String s = null;
        lock_full.lock();
        s = dateFormat_full.format(date);
        lock_full.unlock();
        return s;
    }

    public static String formatFullCurrentDate() {
        return formatFull(new Date());
    }

    private static DateFormat dateFormat_ms = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    private static Lock lock_ms = new ReentrantLock();
    public static String formatMs(Date date) {
        String s = null;
        lock_ms.lock();
        s = dateFormat_ms.format(date);
        lock_ms.unlock();
        return s;
    }

    public static String formatMsCurrentDate() {
        return formatMs(new Date());
    }

    private static DateFormat dateFormat_simple = new SimpleDateFormat("yyyy-MM-dd");
    private static Lock lock_simple = new ReentrantLock();
    public static String formatSimple(Date date) {
        String s = null;
        lock_simple.lock();
        s = dateFormat_simple.format(date);
        lock_simple.unlock();
        return s;
    }

    public static String formatSimple(long time) {
        String s = null;
        lock_simple.lock();
        s = dateFormat_simple.format(time);
        lock_simple.unlock();
        return s;
    }
    public static Date parseSimple(String str) throws ParseException {
        Date date = null;
        lock_simple.lock();
        date = dateFormat_simple.parse(str);
        lock_simple.unlock();
        return date;
    }

    private static DateFormat dateFormat_option = new SimpleDateFormat("yyyyMMdd");
    private static Lock lock_option = new ReentrantLock();
    public static String formatOption(Date date) {
        String s = null;
        lock_option.lock();
        s = dateFormat_option.format(date);
        lock_option.unlock();
        return s;
    }

    public static String formatOption(long time) {
        String s = null;
        lock_option.lock();
        s = dateFormat_option.format(time);
        lock_option.unlock();
        return s;
    }

    public static Date parseOption(String str) throws ParseException {
        Date date = null;
        lock_option.lock();
        date = dateFormat_option.parse(str);
        lock_option.unlock();
        return date;
    }

    private static DateFormat dateFormat_Simple_option = new SimpleDateFormat("MMdd");
    private static Lock lock_Simple_option = new ReentrantLock();
    public static String formatSimpleOption(Date date) {
        String s = null;
        lock_Simple_option.lock();
        s = dateFormat_Simple_option.format(date);
        lock_Simple_option.unlock();
        return s;
    }
    public static Date parseSimpleOption(String str) throws ParseException {
        Date date = null;
        lock_Simple_option.lock();
        date = dateFormat_Simple_option.parse(str);
        lock_Simple_option.unlock();
        return date;
    }

    private static DateFormat dateFormat_tight_standard = new SimpleDateFormat("yyyyMMddHHmmss");
    private static Lock lock_tight_standard = new ReentrantLock();
    public static String formatTightStard(long time) {
        String s = null;
        lock_tight_standard.lock();
        s = dateFormat_tight_standard.format(time);
        lock_tight_standard.unlock();
        return s;
    }

    public static Date parseTightStandard(String str) throws ParseException {
        Date date = null;
        lock_tight_standard.lock();
        date = dateFormat_tight_standard.parse(str);
        lock_tight_standard.unlock();
        return date;
    }

    private static DateFormat dateFormat_tight = new SimpleDateFormat("yyyyMMddHHmm");
    private static Lock lock_tight = new ReentrantLock();
    public static String formatTight(Date date) {
        String s = null;
        lock_tight.lock();
        s = dateFormat_tight.format(date);
        lock_tight.unlock();
        return s;
    }

    public static Date parseTight(String str) throws ParseException {
        Date date = null;
        lock_tight.lock();
        date = dateFormat_tight.parse(str);
        lock_tight.unlock();
        return date;
    }

    public static String formatGMTStandard(Date date) {
        String s = null;
        lock_gmt_dateFormat_standard.lock();
        s = gmt_dateFormat_standard.format(date);
        lock_gmt_dateFormat_standard.unlock();
        return s;
    }
    public static String formatGMTStandard(long time) {
        String s = null;
        lock_gmt_dateFormat_standard.lock();
        s = gmt_dateFormat_standard.format(time);
        lock_gmt_dateFormat_standard.unlock();
        return s;
    }
    public static Date parseGMTStandard(String str) throws ParseException {
        Date date = null;
        lock_gmt_dateFormat_standard.lock();
        date = gmt_dateFormat_standard.parse(str);
        lock_gmt_dateFormat_standard.unlock();
        return date;
    }

    private static DateFormat gmt_dateFormat_simple = new SimpleDateFormat("yyyyMMdd");
    private static DateFormat gmt_dateFormat_standard = new SimpleDateFormat("yyyyMMddHHmmss");
    private static TimeZone GMT = TimeZone.getTimeZone("GMT");
    static {
        gmt_dateFormat_simple.setTimeZone(GMT);
        gmt_dateFormat_standard.setTimeZone(GMT);
    }
    private static Lock lock_gmt_dateFormat_simple = new ReentrantLock();
    private static Lock lock_gmt_dateFormat_standard = new ReentrantLock();
    public static String formatGMTSimple(Date date) {
        String s = null;
        lock_gmt_dateFormat_simple.lock();
        s = gmt_dateFormat_simple.format(date);
        lock_gmt_dateFormat_simple.unlock();
        return s;
    }
    public static String formatGMTSimple(long time) {
        String s = null;
        lock_gmt_dateFormat_simple.lock();
        s = gmt_dateFormat_simple.format(time);
        lock_gmt_dateFormat_simple.unlock();
        return s;
    }

    public static Date parseGMTSimple(String str) throws ParseException {
        Date date = null;
        lock_gmt_dateFormat_simple.lock();
        date = gmt_dateFormat_simple.parse(str);
        lock_gmt_dateFormat_simple.unlock();
        return date;
    }

    /**
     * 返回某个date,指定某个字段(比如月,星期)的第一天
     *
     * @param date  2015-01-05 12:13:14.234
     * @param date  2015-01-05 12:13:14.234
     * @param field Calendar.MONTH
     * @return 2015-01-01 00:00:00.000
     */
    public static Date getFirstDate(Date date, int field) {
        Calendar c = Calendar.getInstance();
        c.setTimeZone(TimeZone.getDefault());
        c.setTime(date);
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        c.set(field, 1);
        return c.getTime();
    }

    /**
     * 2015-01-05 12:13:14 m1 -> 2015-01-05 12:13:00
     *
     * @param date  2015-01-05 12:13:14.234
     * @param field Calendar.MONTH
     * @return 2015-01-01 00:00:00.000
     */
    public static Date getPreEndTime(Date date, Period field) {
        switch (field) {
            case m1:
            case m5:
            case m15:
            case m30:
            case h1:
            case h4:
                return getFirstTime(date, field);
            case close:
                return date;
        }
        return date;
    }

    /**
     * 返回某个date,返回指定区间的最开始时间,这个方法很牛逼啊，很有用
     * 2015-01-05 12:13:14 m1 -> 2015-01-05 12:13:00
     *
     * @param date  2015-01-05 12:13:14.234
     * @param field Calendar.MONTH
     * @return 2015-01-01 00:00:00.000
     */
    public static Date getFirstTime(Date date, Period field) {
        Calendar c = Calendar.getInstance();
        c.setTimeZone(TimeZone.getDefault());
        c.setTime(date);
        //反正秒种和毫秒怎么都要置0的
        c.set(Calendar.MILLISECOND, 0);
        c.set(Calendar.SECOND, 0);
        int minute = c.get(Calendar.MINUTE);
        int hour = c.get(Calendar.HOUR_OF_DAY);
        switch (field) {
            case m1:
                //1分钟的firstTime只需要把秒和毫秒置0就可以了
                break;
            case m5:
                //如果是5分钟，就比较难搞了
                minute = minute - minute % 5;
                c.set(Calendar.MINUTE, minute);
                break;
            case m15:
                //15分钟参照5分钟逻辑
                minute = minute - minute % 15;
                c.set(Calendar.MINUTE, minute);
                break;
            case m30:
                //30分钟参照5分钟逻辑
                minute = minute - minute % 30;
                c.set(Calendar.MINUTE, minute);
                break;
            case h1:
                //1小时只需要把分钟置0就可以了
                c.set(Calendar.MINUTE, 0);
                break;
            case h4:
                //4小时需要把分钟置0，小时减去4的模
                c.set(Calendar.MINUTE, 0);
                hour = hour - hour % 4;
                c.set(Calendar.HOUR_OF_DAY, hour);
                break;
            case daily:
                //天的需要把小时，分钟都置0
                c.set(Calendar.MINUTE, 0);
                c.set(Calendar.HOUR_OF_DAY, 0);
                break;
            case close:
                //收盘时间的beginTime要把日期倒推一天
                //如果是夏令时，则返回上一天的21:30
                c.set(Calendar.MINUTE, 30);
                int beginHour = TimeSeason.Summer == CalendarUtils.getTimeSeason(date) ? 4 : 5;
                if (c.get(Calendar.HOUR_OF_DAY) < beginHour) {
                    c.add(Calendar.DATE, -1);
                }
                if (TimeSeason.Summer == CalendarUtils.getTimeSeason(date)) {
                    c.set(Calendar.HOUR_OF_DAY, 21);
                } else {
                    c.set(Calendar.HOUR_OF_DAY, 22);
                }
                break;
        }
        return c.getTime();
    }


    /**
     * 返回某个date,返回指定区间的最后时间
     * 2015-01-05 12:13:14 m1 -> 2015-01-05 12:13:00
     *
     * @param date  2015-01-05 12:13:14.234
     * @param field Calendar.MONTH
     * @return 2015-01-01 00:00:00.000
     */
    public static Date getLastTime(Date date, Period field) {
        Date firstTime = getFirstTime(date, field);
        Calendar c = Calendar.getInstance();
        c.setTime(firstTime);
        switch (field) {
            case m1:
                c.add(Calendar.MINUTE, 1);
                break;
            case m5:
                c.add(Calendar.MINUTE, 5);
                break;
            case m15:
                c.add(Calendar.MINUTE, 15);
                break;
            case m30:
                c.add(Calendar.MINUTE, 30);
                break;
            case h1:
                c.add(Calendar.HOUR_OF_DAY, 1);
                break;
            case h4:
                c.add(Calendar.HOUR_OF_DAY, 4);
                break;
            case close:
                int beginHour = TimeSeason.Summer == CalendarUtils.getTimeSeason(date) ? 4 : 5;
                if (c.get(Calendar.HOUR_OF_DAY) >= beginHour) {
                    c.add(Calendar.DATE, 1);
                }
                c.set(Calendar.MINUTE, 0);
                if (TimeSeason.Summer == CalendarUtils.getTimeSeason(date)) {
                    c.set(Calendar.HOUR_OF_DAY, 4);
                } else {
                    c.set(Calendar.HOUR_OF_DAY, 5);
                }
        }
        return c.getTime();
    }

    /**
     * 返回昨天
     *
     * @return 2015-01-04 00:00:00.000
     * @e.g current time:2015-01-05 12:13:14.234
     */
    public static Date getYestoday() {
        Calendar c = Calendar.getInstance();
        c.set(Calendar.HOUR, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        c.add(Calendar.DATE, -1);
        return c.getTime();
    }

    /**
     * 返回两个时间制定字段的差值
     *
     * @param begin 2015-01-05 12:13:14.234
     * @param end   2015-01-06 12:13:14.234
     * @param field Calendar.Day
     * @return 1
     */
    public static int getDiff(Date begin, Date end, int field) {
        switch (field) {
            case Calendar.MILLISECOND:
                return (int) (end.getTime() - begin.getTime());
            case Calendar.SECOND:
                return (int) ((end.getTime() - begin.getTime()) / 1000);
            case Calendar.MINUTE:
                return (int) ((end.getTime() - begin.getTime()) / 60000);
            case Calendar.HOUR:
                return (int) ((end.getTime() - begin.getTime()) / 3600000);
            case Calendar.DATE:
                return (int) ((end.getTime() - begin.getTime()) / 86400000);
            default:
                return (int) ((end.getTime() - begin.getTime()) / 86400000);
        }
    }

    /**
     * 返回增加或者减少几天
     *
     * @param date
     * @param days
     * @return
     */
    public static Date addDate(Date date, int days) {
        Calendar c = Calendar.getInstance();
        c.setTime(date);
        c.set(Calendar.HOUR_OF_DAY, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        c.add(Calendar.DATE, days);
        return c.getTime();
    }

    /**
     * 返回当前时间加上若干天后的date对象
     *
     * @param days 3
     * @return 2015-01-05 00:00:00.000
     * @e.g current time:2015-01-05 12:13:14.234
     */
    public static Date addDate(int days) {
        Calendar c = Calendar.getInstance();
        c.set(Calendar.HOUR, 0);
        c.set(Calendar.MINUTE, 0);
        c.set(Calendar.SECOND, 0);
        c.set(Calendar.MILLISECOND, 0);
        c.add(Calendar.DATE, days);
        return c.getTime();
    }

    public static String getDateFormat(Date date) {
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        return dateFormat.format(date);
    }

    //判断是否周末
    public static boolean isWeekDay(long time) {
        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(time);
        c.setFirstDayOfWeek(1);
        int day_of_week = c.get(Calendar.DAY_OF_WEEK);
        return day_of_week == 1 || day_of_week == 7;
    }

    /**
     * 判断是否外汇交易时间
     * 交易时间判定为：
     * 周一的01:00-23:30
     * 周二到周四的00:30-23:30
     * 周五的00:30-23:00
     *
     * @return
     */
    public static boolean isForexTradingTime(long time) {
        boolean isWeekDay = isWeekDay(time);
        if (isWeekDay) {
            return false;
        }

        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(time);
        int day_of_week = c.get(Calendar.DAY_OF_WEEK);
        int hour_of_day = c.get(Calendar.HOUR_OF_DAY);
        int minute = c.get(Calendar.MINUTE);
        //周一计算
        if (day_of_week == 2) {
            if (hour_of_day >= 1 && hour_of_day <= 22) {
                return true;
            }
            if (hour_of_day == 23 && minute < 30) {
                return true;
            }
        } else if (day_of_week >= 3 && day_of_week <= 5) {//周二到周四计算
            //01点-22点
            if (hour_of_day >= 1 && hour_of_day <= 22) {
                return true;
            }
            //00:30之后
            if (hour_of_day == 0 && minute >= 30) {
                return true;
            }
            //23:30之前
            if (hour_of_day == 23 && minute < 30) {
                return true;
            }
            return true;
        } else if (day_of_week == 6) {
            //01点-22点
            if (hour_of_day >= 1 && hour_of_day <= 22) {
                return true;
            }
            //00:30之后
            if (hour_of_day == 0 && minute >= 30) {
                return true;
            }
        }
        return false;
    }


    public static boolean isRealTradingTimeCurrent() {
        return isRealTradingTime(new Date());
    }

    /**
     * 判断是否真交易时间
     * 就是美股的开盘-收盘前5分钟
     *
     * @return
     */
    public static boolean isRealTradingTime(Date date) {
        TimeSeason timeSeason = getTimeSeason(date);
        Calendar c = Calendar.getInstance();
        c.setTime(date);
        int day_of_week = c.get(Calendar.DAY_OF_WEEK);
        if (day_of_week == 1) {
            //星期天一定不是在交易日
            return false;
        }
        int hour_of_day = c.get(Calendar.HOUR_OF_DAY);
        int minute = c.get(Calendar.MINUTE);
        if (timeSeason == TimeSeason.Summer) {
            //夏令时判断
            //周一到周五晚上21:30-24:00点属于trading_time
            //周二到周六早上00:00-04:00点属于trading_time
            if (day_of_week >= 2 && day_of_week <= 6) {
                if (hour_of_day >= 22) {
                    return true;
                }
                if (hour_of_day == 21 && minute >= 30) {
                    return true;
                }
            }
            if (day_of_week >= 3 && day_of_week <= 7) {
                if (hour_of_day <= 2) {
                    return true;
                }
                if (hour_of_day == 3 && minute <= 55) {
                    return true;
                }
            }
        } else if (timeSeason == TimeSeason.Winter) {
            //冬令时判断
            //周一到周五晚上22:30-24:00点属于trading_time
            //周二到周六早上00:00-05:00点属于trading_time
            if (day_of_week >= 2 && day_of_week <= 6) {
                if (hour_of_day >= 23) {
                    return true;
                }
                if (hour_of_day == 22 && minute >= 30) {
                    return true;
                }
            }
            if (day_of_week >= 3 && day_of_week <= 7) {
                if (hour_of_day <= 3) {
                    return true;
                }
                if (hour_of_day == 4 && minute <= 55) {
                    return true;
                }
            }
        }
        return false;
    }

    public static TimeSeason currentTimeSeason() {
        return getTimeSeason(new Date());
    }

    //是否冬令时
    public static TimeSeason getTimeSeason(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int month = calendar.get(Calendar.MONTH);
        switch (month) {
            case 11:
            case 0:
            case 1:
                return TimeSeason.Winter;
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
                return TimeSeason.Summer;
            case 2://3月
                //3月的第二个周日为界，之前为冬令时，之后为夏令时
                Calendar c = Calendar.getInstance();
                c.setTime(date);
                c.set(Calendar.HOUR_OF_DAY, 0);
                c.set(Calendar.MINUTE, 0);
                c.set(Calendar.SECOND, 0);
                c.set(Calendar.MILLISECOND, 0);
                c.set(Calendar.DAY_OF_MONTH, 1);
                int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);//得到3月第一天是周几
                c.add(Calendar.DATE, dayOfWeek == 1 ? 7 : 15 - dayOfWeek);//得到3月第二个星期天的日期
                Date newDate = c.getTime();
                if (date.compareTo(newDate) >= 0) {
                    return TimeSeason.Summer;
                } else {
                    return TimeSeason.Winter;
                }
            case 10://11月
                Calendar d = Calendar.getInstance();
                d.setTime(date);
                d.set(Calendar.HOUR_OF_DAY, 0);
                d.set(Calendar.MINUTE, 0);
                d.set(Calendar.SECOND, 0);
                d.set(Calendar.MILLISECOND, 0);
                d.set(Calendar.DAY_OF_MONTH, 1);
                int dayOfWeek_d = d.get(Calendar.DAY_OF_WEEK);//得到11月第一天是周几
                d.add(Calendar.DATE, dayOfWeek_d == 1 ? 0 : 8 - dayOfWeek_d);//得到1月第一个星期天的日期
                Date newDate_d = d.getTime();
                if (date.compareTo(newDate_d) >= 0) {
                    return TimeSeason.Winter;
                } else {
                    return TimeSeason.Summer;
                }
        }
        return TimeSeason.Summer;
    }

    /**
     * 得到本月最后一个工作日
     *
     * @return
     */
    public static Date getLastWorkDayByThisMonth(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.MONTH, 1);//月份+1
        calendar.set(Calendar.DAY_OF_MONTH, 1);//天设为一个月的第一天
        calendar.add(Calendar.DATE, -1);//本月最后一天
        calendar.add(Calendar.DAY_OF_MONTH, 1 - calendar.get(Calendar.DAY_OF_WEEK) - 2);//根据月末最后一天是星期几，向前偏移至最近的周日
        Date result = calendar.getTime();
        return result;
    }


    public static void main(String[] args) throws ParseException {
        Date date = new Date();
        Date d = getFirstDate(date, Calendar.DAY_OF_WEEK);
        System.out.println(d);

        Date yes = getYestoday();
        System.out.println(yes);

        DateFormat dateFormat_cn = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
        Date d2000 = dateFormat_cn.parse("2000-01-01");
        System.out.println(d2000 + ":" + d2000.getTime());
    }
}
